/*
 * SoapUI, Copyright (C) 2004-2022 SmartBear Software
 *
 * Licensed under the EUPL, Version 1.1 or - as soon as they will be approved by the European Commission - subsequent 
 * versions of the EUPL (the "Licence"); 
 * You may not use this work except in compliance with the Licence. 
 * You may obtain a copy of the Licence at: 
 * 
 * http://ec.europa.eu/idabc/eupl 
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the Licence is 
 * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
 * express or implied. See the Licence for the specific language governing permissions and limitations 
 * under the Licence. 
 */

package com.eviware.soapui.impl.wsdl.monitor.jettyproxy;

import com.eviware.soapui.SoapUI;
import com.eviware.soapui.impl.wsdl.WsdlProject;
import com.eviware.soapui.impl.wsdl.actions.monitor.SoapMonitorAction.LaunchForm;
import com.eviware.soapui.impl.wsdl.actions.monitor.SoapMonitorAction.SecurityTabForm;
import com.eviware.soapui.impl.wsdl.monitor.CaptureInputStream;
import com.eviware.soapui.impl.wsdl.monitor.JProxyServletWsdlMonitorMessageExchange;
import com.eviware.soapui.impl.wsdl.monitor.SoapMonitorListenerCallBack;
import com.eviware.soapui.impl.wsdl.submit.transports.http.ExtendedHttpMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedGetMethod;
import com.eviware.soapui.impl.wsdl.submit.transports.http.support.methods.ExtendedPostMethod;
import com.eviware.soapui.impl.wsdl.support.http.HttpClientSupport;
import com.eviware.soapui.impl.wsdl.support.http.SoapUIHttpRoute;
import com.eviware.soapui.support.types.StringToStringsMap;
import com.eviware.soapui.support.xml.XmlUtils;
import org.apache.http.Header;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.entity.InputStreamEntity;
import org.apache.http.protocol.BasicHttpContext;
import org.mortbay.util.IO;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.URISyntaxException;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;

public class TunnelServlet extends ProxyServlet {
    private String sslEndPoint;
    private int sslPort = 443;
    private String prot = "https://";

    public TunnelServlet(WsdlProject project, String sslEndpoint, SoapMonitorListenerCallBack listenerCallBack) {
        super(project, listenerCallBack);

        if (!sslEndpoint.startsWith("https")) {
            this.prot = "http://";
        }
        int prefix = sslEndpoint.indexOf("://");
        int c = sslEndpoint.indexOf(prefix, ':');
        if (c > 0) {
            this.sslPort = Integer.parseInt(sslEndpoint.substring(c + 1));
            this.sslEndPoint = sslEndpoint.substring(prefix, c);
        } else {
            if (prefix > 0) {
                this.sslEndPoint = sslEndpoint.substring(prefix + 3);
            }
        }
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        this.config = config;
        this.context = config.getServletContext();
    }

    public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
        listenerCallBack.fireOnRequest(project, request, response);
        if (response.isCommitted()) {
            return;
        }

        ExtendedHttpMethod postMethod;

        // for this create ui server and port, properties.
        InetSocketAddress inetAddress = new InetSocketAddress(sslEndPoint, sslPort);
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        if (httpRequest.getMethod().equals("GET")) {
            postMethod = new ExtendedGetMethod();
        } else {
            postMethod = new ExtendedPostMethod();
        }

        JProxyServletWsdlMonitorMessageExchange capturedData = new JProxyServletWsdlMonitorMessageExchange(project);
        capturedData.setRequestHost(httpRequest.getRemoteHost());
        capturedData.setRequestHeader(httpRequest);
        capturedData.setHttpRequestParameters(httpRequest);
        capturedData.setTargetURL(this.prot + inetAddress.getHostName());

        CaptureInputStream capture = new CaptureInputStream(httpRequest.getInputStream());

        long contentLength = -1;
        // copy headers
        Enumeration<?> headerNames = httpRequest.getHeaderNames();
        while (headerNames.hasMoreElements()) {
            String hdr = (String) headerNames.nextElement();
            String lhdr = hdr.toLowerCase();

            if ("content-length".equals(lhdr)) {
                String val = httpRequest.getHeader(hdr);
                contentLength = Long.parseLong(val);
                continue;
            }

            if ("transfer-encoding".equals(lhdr)) {
                continue;
            }

            if ("host".equals(lhdr)) {
                Enumeration<?> vals = httpRequest.getHeaders(hdr);
                while (vals.hasMoreElements()) {
                    String val = (String) vals.nextElement();
                    if (val.startsWith("127.0.0.1")) {
                        postMethod.addHeader(hdr, sslEndPoint);
                    }
                }
                continue;
            }

            Enumeration<?> vals = httpRequest.getHeaders(hdr);
            while (vals.hasMoreElements()) {
                String val = (String) vals.nextElement();
                if (val != null) {
                    postMethod.addHeader(hdr, val);
                }
            }
        }

        if (postMethod instanceof ExtendedPostMethod) {
            InputStreamEntity entity = new InputStreamEntity(capture, contentLength);
            entity.setContentType(request.getContentType());
            ((ExtendedPostMethod) postMethod).setEntity(entity);
        }

        java.net.URI uri = null;
        try {
            uri = new java.net.URI(this.prot + sslEndPoint);
        } catch (URISyntaxException e) {
            SoapUI.logError(e);
        }

        postMethod.getParams().setParameter(
                SoapUIHttpRoute.SOAPUI_SSL_CONFIG,
                settings.getString(SecurityTabForm.SSLTUNNEL_KEYSTOREPATH, "") + " "
                        + settings.getString(SecurityTabForm.SSLTUNNEL_KEYSTOREPASSWORD, ""));

        setProtocolversion(postMethod, request.getProtocol());

        String path = null;
        if (!sslEndPoint.contains("/")) {
            path = "/";
        } else {
            path = sslEndPoint.substring(sslEndPoint.indexOf("/"), sslEndPoint.length());
        }

        if (uri != null) {
            try {
                postMethod.setURI(URIUtils.createURI(uri.getScheme(), uri.getHost(), uri.getPort(), path, uri.getQuery(),
                        uri.getFragment()));
            } catch (URISyntaxException e) {
                SoapUI.logError(e);
            }
        }

        listenerCallBack.fireBeforeProxy(project, request, response, postMethod);

        if (settings.getBoolean(LaunchForm.SSLTUNNEL_REUSESTATE)) {
            if (httpState == null) {
                httpState = new BasicHttpContext();
            }
            HttpClientSupport.execute(postMethod, httpState);
        } else {
            HttpClientSupport.execute(postMethod);
        }
        capturedData.stopCapture();

        capturedData.setRequest(capture.getCapturedData());
        capturedData.setRawResponseBody(postMethod.getResponseBody());
        capturedData.setResponseHeader(postMethod.getHttpResponse());
        capturedData.setRawRequestData(getRequestToBytes(request.toString(), postMethod, capture));
        capturedData.setRawResponseData(getResponseToBytes(response.toString(), postMethod,
                capturedData.getRawResponseBody()));

        listenerCallBack.fireAfterProxy(project, request, response, postMethod, capturedData);

        StringToStringsMap responseHeaders = capturedData.getResponseHeaders();
        // copy headers to response
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        for (Map.Entry<String, List<String>> headerEntry : responseHeaders.entrySet()) {
            for (String header : headerEntry.getValue()) {
                httpServletResponse.addHeader(headerEntry.getKey(), header);
            }

        }

        IO.copy(new ByteArrayInputStream(capturedData.getRawResponseBody()), httpServletResponse.getOutputStream());

        synchronized (this) {
            listenerCallBack.fireAddMessageExchange(capturedData);
        }

    }


    private byte[] getResponseToBytes(String footer, ExtendedHttpMethod postMethod, byte[] res) {
        String response = footer;

        if (postMethod.hasHttpResponse()) {
            Header[] headers = postMethod.getHttpResponse().getAllHeaders();
            for (Header header : headers) {
                response += header.toString().trim() + "\n";
            }
            response += "\n";
            response += XmlUtils.prettyPrintXml(new String(res));
        }
        return response.getBytes();
    }

    private byte[] getRequestToBytes(String footer, ExtendedHttpMethod postMethod, CaptureInputStream capture) {
        String request = footer;

        // Header[] headers = postMethod.getRequestHeaders();
        // for (Header header : headers)
        // {
        // request += header.toString();
        // }
        request += "\n";
        request += XmlUtils.prettyPrintXml(new String(capture.getCapturedData()));

        return request.getBytes();
    }

}
